
type EventCallback = (...args: any[]) => boolean | void;
type EventHandler = {
    event: EventCallback;
    scope?: any;
    once?: boolean;
};

export default class events<T extends string = string> {
    private _$event: Record<T, EventHandler[]> = {} as Record<T, EventHandler[]>;
    /**
     * 事件绑定
     *
     * @param {T} eventName
     * @param {EventCallback} callback
     * @param {*} [scope]
     * @memberof Events
     */
    on(eventName: T, callback: EventCallback, scope?: any): void {
        if (!this._$event[eventName]) {
            this._$event[eventName] = [];
        }
        this._$event[eventName].push({ event: callback, scope });
    }

    /**
     * 事件只触发一次
     *
     * @param {T} eventName
     * @param {EventCallback} callback
     * @param {*} [scope]
     * @memberof Events
     */
    once(eventName: T, callback: EventCallback, scope?: any): void {
        if (!this._$event[eventName]) {
            this._$event[eventName] = [];
        }
        this._$event[eventName].push({ event: callback, scope, once: true });
    }

    /**
     * 移除事件
     *
     * @param {T} [eventName]
     * @param {EventCallback} [callback]
     * @memberof Events
     */
    off(eventName?: T, callback?: EventCallback): void {
        if (eventName) {
            if (callback) {
                this._removeEvent(eventName, callback);
            } else {
                delete this._$event[eventName];
            }
        } else {
            this._$event = {} as Record<T, EventHandler[]>;
        }
    }

    private _removeEvent(eventName: T, callback: EventCallback): void {
        const handlers = this._$event[eventName] || [];
        const index = handlers.findIndex(h => h.event === callback);
        if (index > -1) {
            handlers.splice(index, 1);
        }
    }

    /**
     * 事件触发
     *
     * @param {T} eventName
     * @param {*} context
     * @param {...any[]} args
     * @memberof Events
     */
    emit(eventName: T, context: any, ...args: any[]): void {
        const handlers = this._$event[eventName] || [];
        const onceHandlers: EventHandler[] = [];
        
        handlers.forEach(handler => {
            const continueExecution = handler.event.apply(handler.scope || context, args);
            if (handler.once) onceHandlers.push(handler);
            if (continueExecution === false) return;
        });

        onceHandlers.forEach(handler => this._removeEvent(eventName, handler.event));
    }
}